package sfeir.authform.client.forms;

import sfeir.authform.client.forms.images.AuthImages;
import sfeir.authform.client.service.AuthGWTService;
import sfeir.authform.client.service.AuthGWTServiceAsync;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DisclosurePanel;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.PasswordTextBox;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;

/**
 * Generic login form with methods to enable : forgotten password support, other
 * ways to login behaviour (openid, google, yahoo, OAuth)
 * 
 * @author sfeir
 * 
 */
public abstract class LoginForm extends Composite {

    private boolean forgottenPasswordEnabled = false, forgottenPasswordActive = false;;
    private boolean loginAlternativesEnable = false;
    private boolean loginOpenidEnabled = false, loginOpenidActive = false;
    private boolean loginGoogleEnabled = false;
    private boolean loginYahooEnabled = false;

    private String login;
    private String password;
    private String custLoginLabel, custPasswordLabel, custButton;
    private HTML loginLabel, passwordLabel;
    private TextBox loginField;
    private PasswordTextBox passwordField;
    private Button ok;
    private FlexTable form;
    private VerticalPanel root;

    private HorizontalPanel forgotPanel;

    private VerticalPanel alternativesPanel;// openid/google/...
    HorizontalPanel openidPanel, googlePanel, yahooPanel;
    private Image backer, forgotLogo, openidLogo, googleLogo, yahooLogo;
    private Anchor forgotLink, openidLink, googleLink, yahooLink;
    private AuthImages images;

    private KeyPressHandler keyHandler;
    private ClickHandler clickHandler;

    private final static int ROW_EMAIL = 0;
    private final static int ROW_PASSWD = 1;
    private final static int COL_LABEL = 0;
    private final static int COL_WIDGET = 1;
    private final static int COL_IMAGE = 2;

    /**
     * Method to redefine in order to do what you want once the forms is to be
     * sent.
     * 
     * @param login
     *            the login as it was typed by the user
     * @param passwd
     *            the md5 hash of the users's password
     */
    public abstract void sendLogin(String login, String password);

    /**
     * Send a mail to the given login address with a new generated password.
     * Override this method to implement your own forgotten password system
     * 
     * @param login
     */
    public void getLostPassword(String login) {
        AuthGWTServiceAsync service = GWT.create(AuthGWTService.class);
        service.getLostPassword(login, new AsyncCallback<Boolean>() {
            @Override
            public void onFailure(Throwable caught) {
                Window.alert("ERROR : " + caught.getMessage());
            }

            @Override
            public void onSuccess(Boolean result) {
                if (result)// FIXME replace theses by a nice message in the form
                    Window.alert("your new password has been sent");
                else {
                    Window.alert("unkown user");
                }
            }
        });
    }

    /**
     * Create a basic login form with login/password fields.
     */
    public LoginForm() {
        root = new VerticalPanel();
        form = new FlexTable();
        loginAlternativesEnable = false;

        /*
         * STEP 1 : we implements the handlers.
         */
        keyHandler = new KeyPressHandler() {
            @Override
            public void onKeyPress(KeyPressEvent event) {
                Object source = event.getSource();
                if (source == loginField) {
                    if (loginOpenidActive && event.getCharCode() == KeyCodes.KEY_ENTER) {
                        sendLogin(login, "openid");
                    }
                } else if (source == passwordField) {
                    if (event.getCharCode() == KeyCodes.KEY_ENTER) {
                        login = loginField.getText().trim();
                        if (forgottenPasswordActive) {
                            getLostPassword(login);
                        } else {
                            password = passwordField.getText();
                            password = Natives.md5(password);
                            sendLogin(login, password);
                        }
                    }
                }
            }
        };

        clickHandler = new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                Object source = event.getSource();
                if (source == ok) {
                    login = loginField.getText().trim();
                    if (forgottenPasswordActive) {
                        getLostPassword(login);
                    } else if (loginOpenidActive) {
                        sendLogin(login, "openid");
                    } else {
                        password = passwordField.getText();
                        password = Natives.md5(password);
                        sendLogin(login, password);
                    }
                } else {
                    if (source == forgotLink || source == forgotLogo) {
                        forgottenPasswordActive = true;
                        loginOpenidActive = false;
                        setBacker();
                        loginLabel.setText("Email :");
                        passwordLabel.setText("You'll receive new password at your subscription address");
                        passwordLabel.addStyleName("small");
                        passwordField.setVisible(false);
                        form.getFlexCellFormatter().setColSpan(ROW_PASSWD, COL_LABEL, 3);
                        ok.setText("Send");
                    } else if (isLoginAlternativesEnable()) {
                        if (source == openidLogo || source == openidLink) {
                            loginOpenidActive = true;
                            forgottenPasswordActive = false;
                            setBacker();
                            loginLabel.setText("OpenID :");
                            passwordLabel.setText("(e.g. http://username.myopenid.com)");
                            passwordLabel.addStyleName("small");
                            passwordField.setVisible(false);
                            form.getFlexCellFormatter().setColSpan(ROW_PASSWD, COL_LABEL, 3);
                            ok.setText(custButton);
                        } else if (source == googleLogo || source == googleLink) {
                            // TODO Google login
                        } else if (source == yahooLogo || source == yahooLink) {
                            // TODO Yahoo login
                        }
                    }
                }
            }
        };

        /*
         * STEP 2 : We build the form
         */
        custLoginLabel = "Email :";
        custPasswordLabel = "Password :";
        custButton = "Sign in";

        // EMAIL
        setLoginField();

        // PASSWORD
        setPasswordField();

        // BUTTONS
        HorizontalPanel buttonsPanel = new HorizontalPanel();
        buttonsPanel.setSpacing(5);

        if (images == null)
            images = GWT.create(AuthImages.class);

        forgotPanel = new HorizontalPanel();
        forgotPanel.setSpacing(5);
        forgotLogo = images.password().createImage();
        forgotLogo.addClickHandler(clickHandler);
        forgotLink = new Anchor("forgot password ?", false);
        forgotLink.addClickHandler(clickHandler);
        forgotPanel.add(forgotLogo);
        forgotPanel.add(forgotLink);

        ok = new Button();
        ok.setText(custButton);
        ok.addClickHandler(clickHandler);

        buttonsPanel.add(forgotPanel);
        buttonsPanel.add(ok);

        buttonsPanel.setCellHorizontalAlignment(forgotPanel, HasAlignment.ALIGN_LEFT);
        buttonsPanel.setCellHorizontalAlignment(ok, HasAlignment.ALIGN_RIGHT);
        buttonsPanel.setWidth("100%");

        root.add(form);
        root.add(buttonsPanel);
        root.setCellWidth(buttonsPanel, "100%");

        setForgottenPasswordEnabled(false);

        initWidget(root);
    }

    /**
     * Create a login form with given labels
     * 
     * @param labelLogin
     *            the text that will describe the login TextBox
     * @param labelPassword
     *            the text that will describe the password TextBox
     * @param labelButton
     *            the text that will appear in the Button (can be html)
     */
    public LoginForm(String labelLogin, String labelPassword, String labelButton) {
        this();
        custLoginLabel = labelLogin;
        custPasswordLabel = labelPassword;
        custButton = labelButton;
        loginLabel.setHTML(custLoginLabel);
        passwordLabel.setHTML(custPasswordLabel);
        ok.setHTML(custButton);
    }

    /**
     * Create the whole login field and add it to the form
     */
    private void setLoginField() {
        loginLabel = new HTML(custLoginLabel);
        loginField = new TextBox();
        loginField.setWidth("15em");
        loginField.addKeyPressHandler(keyHandler);
        form.setWidget(ROW_EMAIL, COL_LABEL, loginLabel);
        form.setWidget(ROW_EMAIL, COL_WIDGET, loginField);
    }

    /**
     * Create the whole password field and add it to the form
     */
    private void setPasswordField() {
        passwordLabel = new HTML(custPasswordLabel);
        passwordField = new PasswordTextBox();
        passwordField.setWidth("15em");
        passwordField.addKeyPressHandler(keyHandler);
        form.setWidget(ROW_PASSWD, COL_LABEL, passwordLabel);
        form.setWidget(ROW_PASSWD, COL_WIDGET, passwordField);
    }

    /**
     * Add an icon near the login textbox to let the user go back to the classic
     * login form. The backer is set when the user click either on the
     * "forgotten password" link, or on an alternative login button
     */
    private void setBacker() {
        if (images == null)
            images = GWT.create(AuthImages.class);
        backer = images.application_form().createImage();
        backer.setTitle("Click here to go back to the login form");
        backer.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                forgottenPasswordActive = false;
                loginOpenidActive = false;
                form.removeCell(ROW_EMAIL, COL_IMAGE);
                setLoginField();
                setPasswordField();
                form.getFlexCellFormatter().setColSpan(ROW_PASSWD, COL_LABEL, 1);
                ok.setText(custButton);
            }
        });
        form.setWidget(ROW_EMAIL, COL_IMAGE, backer);
    }

    /**
     * Test whither the email as the good format or not Use this method if the
     * login is an email address to check the validity of this address
     * 
     * @return True is the format is valid, False otherwise
     */
    public boolean checkEmailFormat() {
        login = loginField.getText();
        return (login
                .matches("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"));
    }

    /**
     * Indicates if "I forgot my password" option appears or not
     * 
     * @return
     */
    public boolean isForgottenPasswordEnabled() {
        return forgottenPasswordEnabled;
    }

    /**
     * Add or remove the "I forgot my password" option If this feature is
     * enable, then a <code>null</code> password will be sent with the login.
     * 
     * @param forgottenPasswordEnabled
     *            Set <code>true</code> if you want the option to appear, False
     *            otherwise
     */
    public void setForgottenPasswordEnabled(boolean forgottenPasswordEnabled) {
        this.forgottenPasswordEnabled = forgottenPasswordEnabled;
        if (this.forgottenPasswordEnabled) {
            forgotPanel.setVisible(true);

        } else
            forgotPanel.setVisible(false);
    }

    /**
     * Whither alternative login ways are enabled or not (openID, google, yahoo)
     * 
     * @return
     */
    private boolean isLoginAlternativesEnable() {
        return loginAlternativesEnable;
    }

    /**
     * Enable or disable alternative ways to login OpenId, Google, Yahoo
     * Create/show or hide the panel containing the specific modules.
     * 
     * @param otherLoginSystemEnable
     */
    private void setLoginAlternativesEnable(boolean otherLoginSystemEnable) {
        this.loginAlternativesEnable = otherLoginSystemEnable;
        if (this.loginAlternativesEnable) {
            if (alternativesPanel == null) {
                DisclosurePanel alternatives = new DisclosurePanel("Others ways to login :", true);
                alternatives.setAnimationEnabled(true);
                alternativesPanel = new VerticalPanel();
                alternatives.add(alternativesPanel);
                root.add(alternatives);
            } else
                alternativesPanel.setVisible(true);
        } else {
            if (alternativesPanel != null) {
                alternativesPanel.setVisible(false);
            }
        }
    }

    /**
     * Whither the openID panel is shown or not
     * 
     * @return
     */
    public boolean isLoginOpenidEnabled() {
        return loginOpenidEnabled;
    }

    /**
     * Enable or disable the Openid login method on this form.
     * 
     * @param openidEnabled
     */
    public void setLoginOpenidEnabled(boolean openidEnabled) {
        this.loginOpenidEnabled = openidEnabled;

        if (this.loginOpenidEnabled) {
            if (!isLoginAlternativesEnable())
                setLoginAlternativesEnable(true);
            if (openidPanel == null) {

                if (images == null)
                    images = GWT.create(AuthImages.class);

                openidPanel = new HorizontalPanel();
                openidPanel.setSpacing(5);
                openidLogo = images.openid().createImage();
                openidLink = new Anchor("signin in with OpenID", false);
                openidPanel.add(openidLogo);
                openidPanel.add(openidLink);

                openidLink.addClickHandler(clickHandler);
                openidLogo.addClickHandler(clickHandler);

                alternativesPanel.add(openidPanel);
            }
        } else {
            if (alternativesPanel != null && openidPanel != null) {
                alternativesPanel.remove(openidPanel);
            }
        }
    }

    /**
     * Whither the google panel is shown or not
     * 
     * @return
     */
    public boolean isLoginGoogleEnabled() {
        return loginGoogleEnabled;
    }

    /**
     * Enable or disable the Google login method on this form.
     * 
     * @param loginGoogleEnabled
     */
    public void setLoginGoogleEnabled(boolean loginGoogleEnabled) {
        this.loginGoogleEnabled = loginGoogleEnabled;

        if (this.loginGoogleEnabled) {
            if (!isLoginAlternativesEnable())
                setLoginAlternativesEnable(true);
            if (googlePanel == null) {

                if (images == null)
                    images = GWT.create(AuthImages.class);

                googlePanel = new HorizontalPanel();
                googlePanel.setSpacing(5);
                googleLogo = images.google().createImage();
                googleLink = new Anchor("signin in with a Google account", false);
                googlePanel.add(googleLogo);
                googlePanel.add(googleLink);

                googleLink.addClickHandler(clickHandler);
                googleLogo.addClickHandler(clickHandler);

                alternativesPanel.add(googlePanel);
            }
        } else {
            if (alternativesPanel != null && googlePanel != null) {
                alternativesPanel.remove(googlePanel);
            }
        }
    }

    /**
     * Whither the yahoo panel is shown or not
     * 
     * @return
     */
    public boolean isLoginYahooEnabled() {
        return loginYahooEnabled;
    }

    /**
     * Enable or disable the Yahoo login method on this form.
     * 
     * @param loginYahooEnabled
     */
    public void setLoginYahooEnabled(boolean loginYahooEnabled) {
        this.loginYahooEnabled = loginYahooEnabled;

        if (this.loginYahooEnabled) {
            if (!isLoginAlternativesEnable())
                setLoginAlternativesEnable(true);
            if (yahooPanel == null) {

                if (images == null)
                    images = GWT.create(AuthImages.class);

                yahooPanel = new HorizontalPanel();
                yahooPanel.setSpacing(5);
                yahooLogo = images.yahoo().createImage();
                yahooLink = new Anchor("signin in with Yahoo", false);
                yahooPanel.add(yahooLogo);
                yahooPanel.add(yahooLink);

                yahooLink.addClickHandler(clickHandler);
                yahooLogo.addClickHandler(clickHandler);

                alternativesPanel.add(yahooPanel);
            }
        } else {
            if (alternativesPanel != null && yahooPanel != null) {
                alternativesPanel.remove(yahooPanel);
            }
        }
    }

}
